package cn.infrabase.http;

import cn.infrabase.core.util.StringUtils;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 网址参数。
 *
 * @author Weimin Gao
 * @since 1.0.0
 **/
@Data
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
public class UrlParameter {

	private static final String Q = "?";

	private static final String A = "&";

	private static final String E = "=";

	/**
	 * 网址查询字符串（键值对数据）。
	 */
	private Map<String, List<String>> data = new LinkedHashMap<>(1);

	/**
	 * 构造函数。
	 */
	UrlParameter() {
		super();
	}

	/**
	 * 根据给定的网址查询字符串创建网址参数对象（参数值不进行解码）。
	 *
	 * @param querystring 给定的网址查询字符串，格式：key1=value2&key2=value2。
	 * @return 网址参数对象。
	 */
	public static UrlParameter of(@Nonnull String querystring) {
		UrlParameter ret = new UrlParameter();
		update(ret, querystring);
		return ret;
	}

	/**
	 * 根据给定的网址查询字符串更新当前网址参数对象（参数值不进行解码）。
	 *
	 * @param querystring 给定的网址查询字符串，格式：key1=value2&key2=value2。
	 */
	public void update(String querystring) {
		update(this, querystring);
	}

	/**
	 * 返回网址查询字符串键。
	 *
	 * @return 网址查询字符串键。
	 */
	public List<String> keys() {
		return new ArrayList<>(data.keySet());
	}

	/**
	 * 返回真表示包含给定的键。
	 *
	 * @param key 给定的键。
	 * @return 真包含给定的键。
	 */
	public boolean contains(String key) {
		return data.containsKey(key);
	}

	/**
	 * 根据给定的参数键返回对应的值（参数值进行解码），如果给定的参数键不存在时则返回null值。
	 *
	 * @param key 给定的参数键。
	 * @return 参数键对应的值。
	 */
	public List<String> get(String key) {
		return get(key, true);
	}

	/**
	 * 根据给定的参数键返回对应的值。
	 *
	 * @param key      给定的参数键。
	 * @param isDecode 参数值是否进行解码。
	 * @return 参数键对应的值。
	 */
	public List<String> get(String key, boolean isDecode) {
		List<String> values = data.get(key);
		if (values != null && !values.isEmpty()) {
			List<String> ret = new ArrayList<>(values.size());
			for (String value : values) {
				ret.add(decode(value, isDecode));
			}
			return ret;
		}
		return values;
	}

	/**
	 * 返回网址查询字符串（参数值不进行编码）。
	 *
	 * @return 网址查询字符串。
	 */
	public String query() {
		return query(false);
	}

	/**
	 * 返回网址查询字符串。
	 *
	 * @param isEncode 参数值是否进行编码。
	 * @return 网址查询字符串。
	 */
	public String query(boolean isEncode) {
		StringBuilder ret = new StringBuilder();
		List<String> keys = keys();
		for (String key : keys) {
			List<String> values = data.get(key);
			update(ret, key, values, isEncode);
		}
		return ret.toString();
	}

	@Override
	public String toString() {
		return query();
	}

	// -----------------------------------------------------------------------------------------------------------------

	private static void update(@Nonnull UrlParameter parameter, @Nonnull String querystring) {
		Map<String, List<String>> data = new LinkedHashMap<>();
		if (StringUtils.hasText(querystring)) {
			String query = querystring;
			if (querystring.startsWith(Q)) {
				query = querystring.substring(1);
			}
			if (!StringUtils.hasText(query)) {
				return;
			}
			List<String> values;
			String[] items = query.split(A);
			for (String item : items) {
				// a=b=c
				String value = null;
				String key = item;
				if (item.contains(E)) {
					key = item.substring(0, item.indexOf(E));
					value = item.substring(item.indexOf(E) + 1);
				}
				if (!data.containsKey(key)) {
					data.put(key, new ArrayList<>());
				}
				values = data.get(key);
				values.add(value);
			}
		}
		parameter.data = data;
	}

	private void update(StringBuilder ret, String key, List<String> values, boolean encode) {
		if (values.isEmpty()) {
			ret.append(key);
		} else {
			for (String value : values) {
				if (!StringUtils.isEmpty(ret)) {
					ret.append("&");
				}
				if (value == null) {
					ret.append(key);
				} else {
					ret.append(String.format("%s=%s", key, encode(value, encode)));
				}
			}
		}
	}

	// -----------------------------------------------------------------------------------------------------------------

	private static String decode(String value, boolean isDecode) {
		if (isDecode) {
			return UrlDecoder.decode(value);
		} else {
			return value;
		}
	}

	private static String encode(String value, boolean isEncode) {
		if (isEncode) {
			return UrlEncoder.encode(value);
		} else {
			return value;
		}
	}
}
